package se.webbzon.boltzmann.login;

import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;

import se.webbzon.oschi01.aes.HexOutputStream;

public abstract class HTTPRequest implements Closeable, AutoCloseable {
	
	// The default connection timeout (in milliseconds)
	private static final int defaultTimeout = 10000;
	
	// The default user agent
	private static final String defaultUserAgent = "Boltzmann";
	
	// The access cookie name
	private static final String accessCookieName = "PHPSESSID";
	
	// The underlying connection associated with this HTTP request.
	private final HttpURLConnection conn;
	
	// The response associated with this request
	private HTTPResponse response;
	
	/*============================================================
	Constructors
	============================================================*/
	
	/** Creates a simple HTTP request to the given URL. **/
	public HTTPRequest(URL url) throws IOException {
		this(url,null,null);
	}
	
	/** Creates a HTTP request to the given URL with the given array of cookies. **/
	public HTTPRequest(URL url, HTTPCookie[] cookies) throws IOException {
		this(url,null,cookies);
	}
	
	/** Creates a HTTP request to the given URL with the given login access. **/
	public HTTPRequest(URL url, LoginAccess access) throws IOException {
		this(url,access,null);
	}
	
	/** Creates a HTTP request to the given URL with the given array of
	 * cookies and login access. **/
	public HTTPRequest(URL url, LoginAccess access, HTTPCookie[] cookies) throws IOException {
		conn = (HttpURLConnection) url.openConnection();
		conn.setReadTimeout(defaultTimeout);
		conn.setInstanceFollowRedirects(false); 
		conn.addRequestProperty("Accept-Language", "en-US,en;q=0.8");
		conn.addRequestProperty("User-Agent", defaultUserAgent);
		boolean hasAccess = access != null && access.getSessionId() != null;
		boolean hasCookies = cookies != null && cookies.length > 0;
		if (hasAccess || hasCookies) {
			StringBuilder builder = new StringBuilder();
			boolean first = true;
			if (hasAccess) {
				first = false;
				builder.append(accessCookieName + "=" + HexOutputStream.toHex(access.getSessionId()));
			}
			if (hasCookies) {
				for (HTTPCookie cookie : cookies) {
					if (first) {
						first = false;
						builder.append(cookie.getName() + "=" + cookie.getValue());
					} else
						builder.append("; " + cookie.getName() + "=" + cookie.getValue());
				}
			}
			conn.addRequestProperty("Cookie", builder.toString());
		}
		response = null;
	}
	
	/*============================================================
	Public Methods
	============================================================*/
	
	/** Sends the HTTP request to the server. **/
	public HTTPResponse send() throws IOException {
		return send(defaultTimeout);
	}
	
	/** Sends the HTTP request to the server. **/
	public HTTPResponse send(int timeout) throws IOException {
		if (response != null)
			throw new IllegalStateException("The request has already been sent!");
		conn.setReadTimeout(timeout);
		InputStream is = conn.getInputStream();
		ArrayList<HTTPCookie> cookies = new ArrayList<HTTPCookie>();
		Map<String,List<String>> fields = conn.getHeaderFields();
		for (Entry<String,List<String>> field : fields.entrySet()) {
			String key = field.getKey();
			if (key != null && key.equalsIgnoreCase("set-cookie")) {
				for (String value : field.getValue()) {
					cookies.add(parseCookie(value));
				}
			}
		}
		response = new DefaultHTTPResponse(is,cookies);
		return response;
	}
	
	@Override public void close() throws IOException {
		if (response != null)
			response.close();
		conn.disconnect();
	}
	
	/*============================================================
	Protected Methods
	============================================================*/
	
	/** Returns the underlying connection of this HTTP request. **/
	protected HttpURLConnection getHttpURLConnection() {
		return conn;
	}
	
	/** Returns the HTTPResponse associated with this HTTP request. **/
	protected HTTPResponse getHTTPResponse() {
		return response;
	}
	
	/*============================================================
	Private Methods
	============================================================*/

	/** Parses a string to a HTTP cookie. **/
	private static final HTTPCookie parseCookie(String cookie) {
		String cname = null, cvalue = null;
		String domain = null, path = null;
		Date expires = null;
		boolean secure = false, httpOnly = false;
		String[] parameters = cookie.split(";");
		for (String parameter : parameters) {
			String[] nameValue = parameter.split("=");
			String name = nameValue[0].trim();
			String value = nameValue.length > 1 ? nameValue[1].trim() : null;
			if (value != null) {
				// Parameters with values
				if (name.equalsIgnoreCase("domain")) {
					domain = value;
				} else if (name.equalsIgnoreCase("path")) {
					path = value;
				} else if (name.equalsIgnoreCase("expires")) try {
					SimpleDateFormat format = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss 'GMT'", Locale.ENGLISH);
					expires = format.parse(value);
				} catch (ParseException e) {
					e.printStackTrace();
					expires = null;
				} else {
					cname = name;
					cvalue = value;
				}
			} else {
				// Parameters without values
				if (name.equalsIgnoreCase("secure")) {
					secure = true;
				} else if (name.equalsIgnoreCase("httponly")) {
					httpOnly = true;
				} else {
					cname = name;
				}
			}
			
		}
		return new HTTPCookie(cname,cvalue,domain,path,expires,secure,httpOnly);
	}
	
	/*============================================================
	Private Classes
	============================================================*/
	
	private static final class DefaultHTTPResponse extends HTTPResponse {
		
		// The HTTP cookies associated with this response
		private final List<HTTPCookie> cookies;

		public DefaultHTTPResponse(InputStream is, ArrayList<HTTPCookie> cookies) {
			super(is);
			this.cookies = cookies;
		}

		@Override public int cookies() {
			return cookies.size();
		}

		@Override public HTTPCookie getCookie(int n) {
			return cookies.get(n);
		}
		
	}

}
